package com.yhd.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.yhd.entity.TCourse;
import com.yhd.entity.TCourseAbout;
import com.yhd.entity.TCourseBooked;
import com.yhd.entity.dto.CourseBookedDto;
import com.yhd.entity.wx.*;
import com.yhd.enums.TradeStateEnum;
import com.yhd.mapper.CourseAboutMapper;
import com.yhd.mapper.CourseBookedMapper;
import com.yhd.mapper.CourseMapper;
import com.yhd.service.CourseBookedService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WxPayService {
	public static final String UNIFIED_ORDER = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
	@Value("${wechat-pay.appId}")
	private String appId;
	@Value("${wechat-pay.secret}")
	private String secret;
	@Value("${wechat-pay.sp_mchid}")
	private String sp_mchid;
	@Value("${wechat-pay.sp_appid}")
	private String sp_appid;
	@Value("${wechat-pay.sub_appid}")
	private String sub_appid;
	@Value("${wechat-pay.sub_mchid}")
	private String sub_mchid;
	@Value("${wechat-pay.notify_url}")
	private String notify_url;
	@Value("${wechat-pay.apiV3Key}")
	private String apiV3Key;
	@Value("${wechat-pay.serialNo}")
	private String serialNo;
	
	@Autowired
	private CourseBookedService courseBookedService;
	@Autowired
	private CourseMapper courseMapper;
	
	@Autowired
	private CourseBookedMapper courseBookedMapper;
	
	@Autowired
	private CourseAboutMapper courseAboutMapper;
	
	/**
	 * 统一下单
	 */
	public Map<String, String> unifiedOrder(CourseBookedDto bookedDto, String openId) throws Exception {
		UnifiedOrderParam param = getUnifiedOrderParam(openId, bookedDto.getPrice().multiply(new BigDecimal(100)).intValue(),
				bookedDto.getOrderNo(), bookedDto.getCourseDto().getName());
		try {
			log.info("微信下单参数：{}", JSONObject.toJSONString(param));
			HttpResponse response = this.post(JSONObject.toJSONString(param));
			int statusCode = response.getStatusLine().getStatusCode();
			if (statusCode != 200 && statusCode != 204) {
				throw new RuntimeException("下单失败：" + EntityUtils.toString(response.getEntity()));
			}
			log.info("下单返回：{}", EntityUtils.toString(response.getEntity()));
			response.getStatusLine().getStatusCode();
			String bodyAsString = EntityUtils.toString(response.getEntity());
			
			JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
			String prepay_id = jsonObject.get("prepay_id").toString();
			if (StringUtils.isBlank(prepay_id)) {
				throw new RuntimeException("prepay_id 为空");
			}
			return getWxPayParam(prepay_id);
		} catch (Exception e) {
			throw new RuntimeException("下单失败：", e);
		}
		
	}
	
	CloseableHttpResponse post(String param) throws IOException {
		HttpPost httpPost = new HttpPost(UNIFIED_ORDER);
		
		// 请求body参数
		String reqdata = param;
		StringEntity entity = new StringEntity(reqdata, "utf-8");
		entity.setContentType("application/json");
		httpPost.setEntity(entity);
		httpPost.setHeader("Accept", "application/json");
		
		String privateKey = getPrivateKeyStr();
		// 加载商户私钥（privateKey：私钥字符串）
		PrivateKey merchantPrivateKey = PemUtil
				.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
		
		
		// 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
		AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
				new WechatPay2Credentials(sp_mchid, new PrivateKeySigner(serialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
		
		// 初始化httpClient
		CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
				.withMerchant(sp_mchid, serialNo, merchantPrivateKey)
				.withValidator(new WechatPay2Validator(verifier)).build();
		
		//完成签名并执行请求
		CloseableHttpResponse response = httpClient.execute(httpPost);
		return response;
	}
	
	
	/**
	 * 统一下单参数拼接
	 */
	private UnifiedOrderParam getUnifiedOrderParam(String openId, int payTotal, String orderId, String title) {
		// 订单金额
		UnifiedOrderAmount amount = UnifiedOrderAmount.builder().total(payTotal).currency("CNY").build();
		//支付者
		UnifiedOrderPayer payer = UnifiedOrderPayer.builder().sub_openid(openId).build();
		if (StringUtils.isNotBlank(title) && title.length() > 64) {
			title = title.substring(0, 63);
		}
		return UnifiedOrderParam
				.builder()
				.sp_appid(sp_appid)
				.sp_mchid(sp_mchid)
				.sub_appid(appId)
				.sub_mchid(sub_mchid)
				.description(title)
				.out_trade_no(orderId)
				.notify_url(notify_url)
				.amount(amount)
				.payer(payer)
				.build();
	}
	
	/**
	 * 拼接小程序调起微信支付控件的参数
	 *
	 * @param prePayId 预支付编码
	 * @return 参数
	 * @throws Exception 异常
	 */
	private Map<String, String> getWxPayParam(String prePayId) throws Exception {
		String nonceStr = RandomStringUtils.randomAlphanumeric(32);
		long timestamp = System.currentTimeMillis() / 1000;
		//包装prepay_id 等 返给前端由前端拉起支付
		HashMap<String, String> params = new HashMap<>();
		params.put("appId", appId);
		params.put("nonceStr", nonceStr);
		params.put("package", "prepay_id=" + prePayId);
		params.put("signType", "RSA");
		params.put("timeStamp", String.valueOf(timestamp));
		//重新签名
		String signMessage = spliceNewline(appId, timestamp, nonceStr, "prepay_id=" + prePayId);
		String paySign = sign(signMessage.getBytes(StandardCharsets.UTF_8));
		params.put("paySign", paySign);
		return params;
	}
	
	/**
	 * 拼接换行符
	 *
	 * @param param 参数
	 * @return 拼接后的结果
	 */
	private String spliceNewline(Object... param) {
		StringBuilder result = new StringBuilder();
		for (Object str : param) {
			result.append(str).append(StringUtils.LF);
		}
		return result.toString();
	}
	
	/**
	 * 生成签名
	 *
	 * @param message 签名信息
	 * @return 签名
	 * @throws Exception 异常
	 */
	private String sign(byte[] message) throws Exception {
		Signature sign = Signature.getInstance("SHA256withRSA");
		sign.initSign(getPrivateKey());
		sign.update(message);
		return Base64.getEncoder().encodeToString(sign.sign());
	}
	
	/**
	 * 获取私钥。
	 *
	 * @return 私钥对象
	 */
	public static PrivateKey getPrivateKey() throws IOException {
		String privateKey = getPrivateKeyStr();
		// 加载商户私钥（privateKey：私钥字符串）
		PrivateKey merchantPrivateKey = PemUtil
				.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
		return merchantPrivateKey;
	}
	
	public static String getPrivateKeyStr() throws IOException {
		//URL url = WxPayService.class.getClassLoader().getResource("key/private_key.pem");
		//String file = url.getFile();
		//ClassPathResource resource = new ClassPathResource("E:\\private_key.pem");
		//ClassPathResource resource = new ClassPathResource("key/private_key.pem");
		//File file = resource.getFile();
//        FileReader fileReader = new FileReader(file);
//        BufferedReader reader = new BufferedReader(fileReader);
//        StringBuilder sb = new StringBuilder();
//        String content;
//        while ((content = reader.readLine()) != null) {
//            sb.append(content + '\n');
//        }
//        reader.close();
		ClassPathResource resource = new ClassPathResource("key/private_key.pem");
		InputStreamReader intput = null;
		// 修改部分
		intput = new InputStreamReader(resource.getInputStream());
		BufferedReader reader = new BufferedReader(intput);
		StringBuilder sb = new StringBuilder();
		String content;
		while ((content = reader.readLine()) != null) {
			sb.append(content + '\n');
		}
		reader.close();
		String privateKey = sb.toString().replace("-----BEGIN PRIVATE KEY-----", "")
				.replace("-----END PRIVATE KEY-----", StringUtils.EMPTY)
				.replaceAll("\\s+", StringUtils.EMPTY);
		return privateKey;
	}
	
	/**
	 * post请求
	 *
	 * @param url      url地址
	 * @param jsonBody 参数体
	 * @return 返回结果
	 */
	private HttpResponse post(String url, String jsonBody) throws Exception {
		URIBuilder uriBuilder = new URIBuilder(url);
		HttpPost httpPost = new HttpPost(uriBuilder.build());
		//header封装
		httpPost.addHeader("Accept", "application/json");
		httpPost.addHeader("Content-Type", "application/json");
		StringEntity entity = new StringEntity(jsonBody, StandardCharsets.UTF_8);
		httpPost.setEntity(entity);
		//执行方法
		HttpResponse response = buildHttpClientAutoUpdateCertificate().execute(httpPost);
		//状态判断
		if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
			throw new RuntimeException("操作失败");
		}
		return response;
	}
	
	
	/***
	 * 构建httpclient（自动更新证书）
	 * @return HttpClient
	 */
	private HttpClient buildHttpClientAutoUpdateCertificate() throws IOException {
		//不需要传入微信支付证书了
		AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
				new WechatPay2Credentials(sp_mchid, new PrivateKeySigner(serialNo, getPrivateKey())),
				apiV3Key.getBytes(StandardCharsets.UTF_8));
		return WechatPayHttpClientBuilder.create()
				.withMerchant(sp_mchid, serialNo, getPrivateKey())
				.withValidator(new WechatPay2Validator(verifier))
				.build();
	}
	
	
	public void wxPayNotify(HttpServletRequest request) throws Exception {
		String requestBody = getRequestBody(request);
		log.info("收到微信回调:{}", requestBody);
		PaySuccessNoticeVo paySuccessNoticeVo = buildCallbackResult(requestBody, PaySuccessNoticeVo.class);
		String out_trade_no = paySuccessNoticeVo.getOut_trade_no();
		TCourseBooked courseBooked = courseBookedService.selectBookeByOrderNo(out_trade_no);
		//如果订单不是待支付支付状态则直接返回
		if (courseBooked.getStatus() != 1) {
			return;
		}
		//根据支付状态做不同的业务处理
		if (TradeStateEnum.SUCCESS.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
			courseBooked.setStatus(2);
			courseBooked.setPayType(1);
			courseBooked.setSerialNum(paySuccessNoticeVo.getTransaction_id());
			courseBooked.setPayTime(new Date());
			courseBookedService.update(courseBooked);
			//更新课程销售量
			TCourse course = new TCourse();
			course.setId(Integer.parseInt(courseBooked.getCourseId()));
			course = courseMapper.selectByPrimaryKey(course);
			course.setSaleNum(course.getSaleNum() + 1);
			courseMapper.updateByPrimaryKeySelective(course);
			//更新未约课的体验单为后台作废
			Example example = new Example(TCourseBooked.class);
			example.createCriteria().andEqualTo("cuserId", courseBooked.getCuserId())
					.andEqualTo("subType", 1).andEqualTo("courseId", courseBooked.getCourseId());
			List<TCourseBooked> bookeds = courseBookedMapper.selectByExample(example);
			if (CollectionUtils.isEmpty(bookeds)) {
				return;
			}
			List<Integer> bookIds = bookeds.stream().map(val -> val.getId()).collect(Collectors.toList());
			//查询已经约课的订单
			Example aboutExample = new Example(TCourseAbout.class);
			aboutExample.createCriteria().andIn("orderId", bookIds);
			List<TCourseAbout> abouts = courseAboutMapper.selectByExample(aboutExample);
			List<Integer> aboutOrderIds = abouts.stream().map(TCourseAbout::getOrderId).collect(Collectors.toList());
			//筛选出需要作废的订单
			bookIds = bookIds.stream().filter(val -> !aboutOrderIds.contains(val)).collect(Collectors.toList());
			if (!CollectionUtils.isEmpty(bookIds)){
				TCourseBooked tCourseBooked = new TCourseBooked();
				tCourseBooked.setParticipate(0);
				Example updateExample = new Example(TCourseBooked.class);
				updateExample.createCriteria().andIn("id", bookIds);
				courseBookedMapper.updateByExampleSelective(tCourseBooked, updateExample);
			}
		} else if (TradeStateEnum.USER_PAYING.getCode().equals(paySuccessNoticeVo.getTrade_state())) {
			//todo 支付中业务
		}
		
	}
	
	/**
	 * 处理回调接口返回结果
	 *
	 * @param request HttpServletRequest
	 * @param clazz   结果类型
	 * @return 回调接口返回结果
	 * @throws Exception 异常
	 */
	private <T> T buildCallbackResult(String requestBody, Class<T> clazz) throws Exception {
		NoticeVo noticeVo = JSON.parseObject(requestBody, NoticeVo.class);
		//log.info("微信回调 Notice：{}", getRequestBody(request));
		EncryptMessageVo resource = noticeVo.getResource();
		AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
		String result = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8),
				resource.getNonce().getBytes(StandardCharsets.UTF_8),
				resource.getCiphertext());
		log.info("微信回调：data:{}", result);
		return JSON.parseObject(result, clazz);
	}
	
	/**
	 * 解析request中的数据
	 *
	 * @param request HttpServletRequest
	 * @return request中的数据
	 */
	private String getRequestBody(HttpServletRequest request) {
		StringBuilder sb = new StringBuilder();
		try (ServletInputStream stream = request.getInputStream();
		     BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
			String line;
			while ((line = reader.readLine()) != null) {
				sb.append(line);
			}
		} catch (Exception e) {
			throw new RuntimeException("操作失败", e);
		}
		return sb.toString();
	}
	
}
